package com.gupaoedu.vip.proxy.jdkproxy;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * @Auther: tcm
 * @Date: 2020/1/25 19:57
 * @Description:
 */
public class GPProxy {

    public static final String ln = "\r\n";

    public static Object newProxyInstance(GPClassLoader classLoader, Class<?>[] interfaces, GPInvocationHandler h){

        try {

            String src = generateSrc(interfaces);

            String filePath = GPProxy.class.getResource("").getPath();
            File f = new File(filePath + "$Proxy0.java");

            FileWriter fw = new FileWriter(f);
            fw.write(src);
            fw.flush();
            fw.close();

            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manage = compiler.getStandardFileManager(null,null,null);
            Iterable iterable = manage.getJavaFileObjects(f);

            JavaCompiler.CompilationTask task = compiler.getTask(null,manage,null,null,null,iterable);
            task.call();
            manage.close();

            Class proxyClass = classLoader.findClass("$Proxy0");
            Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);
            f.delete();

            return c.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class<?>[] interfaces) {

        StringBuffer sbf = new StringBuffer();
        StringBuffer lbf = new StringBuffer();
        sbf.append("package com.gupaoedu.vip.proxy.jdkproxy;" + ln);
        sbf.append("import java.lang.reflect.*; " + ln);
        sbf.append("public class $Proxy0 implements");
        for (int i=0;i<interfaces.length;i++) {
            sbf.append(" " + interfaces[i].getName());
            if(i<interfaces.length-1){
                sbf.append(",");
            }

            for(Method method:interfaces[i].getMethods()){
                Class<?>[] params = method.getParameterTypes();

                StringBuffer paramNames = new StringBuffer();
                StringBuffer paramValues = new StringBuffer();
                StringBuffer paramClasses = new StringBuffer();

                for(int j=0;j<params.length;j++){
                    Class clazz = params[j];
                    String type = clazz.getName();
                    String paramName = toLowerFirstCase(clazz.getSimpleName());
                    paramNames.append(type + " " + paramName);
                    paramValues.append(paramName);
                    paramClasses.append(clazz.getName() + ".class");
                    if(j>0 && j<params.length-1){
                        paramNames.append(",");
                        paramValues.append(",");
                        paramClasses.append(",");
                    }
                }

                lbf.append("public " + method.getReturnType().getName() + " " + method.getName() + "("
                + paramNames.toString() + "){" + ln);
                lbf.append("try{" + ln);
                lbf.append("Method m = " + interfaces[i].getName() + ".class.getMethod(\"" + method.getName() + "\",new Class[]{"+ paramClasses +"});" + ln);
                lbf.append("}catch(Throwable e){" + ln);
                lbf.append("throw new UndeclardThrowableException(e);" + ln);
                lbf.append("}" + ln);
                lbf.append(getReturnEmptyCode(method.getReturnType()) + ln);
                lbf.append("}" + ln);
            }
        }
        sbf.append("{" + ln);
        sbf.append("private GPInvocationHandler h;" + ln);
        sbf.append("public $Proxy0(GPInvocationHandler h){" + ln);
        sbf.append("this.h = h");
        sbf.append("}" + ln);
        sbf.append(lbf.toString());
        sbf.append("}");

        return sbf.toString();
    }

    private static Map<Class,Class> mappings = new HashMap<>();
    static{
        mappings.put(int.class, Integer.class);
    }

    private static String getReturnEmptyCode(Class<?> returnType) {
        if(mappings.containsKey(returnType)){
            return "return 0;";
        }else if(returnType == void.class){
            return "";
        }else{
            return "return null;";
        }
    }

    private static String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

}
